package com.fooddiary.service.impl;

import java.util.ArrayList;
import java.util.Calendar;
import java.util.Collections;
import java.util.List;
import java.util.Set;
import java.util.TreeSet;

import org.apache.commons.collections.CollectionUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import com.fooddiary.dao.IWeightDao;
import com.fooddiary.model.Eating;
import com.fooddiary.model.EatingType;
import com.fooddiary.model.Model;
import com.fooddiary.model.Weight;
import com.fooddiary.service.IWeightService;
import com.fooddiary.util.AppConstants;
import com.fooddiary.util.AppException;
import com.fooddiary.util.SortPaginationBean;
import com.fooddiary.util.WeightPredicate;
import com.fooddiary.web.form.EatingForm;
import com.fooddiary.web.form.EatingTypeForm;
import com.fooddiary.web.form.WeightForm;

/**
 * 
 * @author Created by andrey.kuznetsov <br>
 * @author Last modified by $Author$ <br>
 * @author Last modified on $Date$ at revision $Revision$ <br>
 */
@Service
public class WeightService extends AbstractService implements IWeightService{

	private final String LOG_ENTITY = "weight";
	
	private final Short OVERWEIGHT = 1;
	private final Short TRENNING = 1;
	
	@Autowired
	IWeightDao weightDao;

	@Override
	@Transactional(readOnly=false)
	public Object[] loadRecords(Model p_model, SortPaginationBean p_sortPagination) throws AppException {
		checkModel( p_model );
		Long dateOfLastRecord = weightDao.getMaxDateOfExistingWeightRecords( p_model );
		List<EatingType> eatingTypes = baseDao.getEnabledEntities( EatingType.class );
		List<Weight> weightRecordsForCreation = null;
		if(dateOfLastRecord != null){
			Calendar previousDate = Calendar.getInstance();
			previousDate.setTimeInMillis( dateOfLastRecord );
			if(!applicationUtils.isToday( previousDate )){
				weightRecordsForCreation = new ArrayList<Weight>();
				while(true){
					if(applicationUtils.isToday( previousDate ) || previousDate.after( Calendar.getInstance() )){
						break;
					}
					previousDate.setTimeInMillis( previousDate.getTimeInMillis() + AppConstants.MILLISECONDS_IN_DAY );
					weightRecordsForCreation.add( createWeightForDate( p_model, previousDate.getTimeInMillis(), eatingTypes ) );
				}
				weightDao.saveOrUpdateAll( weightRecordsForCreation );
			}
		}else{
			weightDao.saveOrUpdate( createWeightForDate( p_model, applicationUtils.getCurrentTimeInMilliseconds(), eatingTypes ) );
		}
		
		List<WeightForm> formList = new ArrayList<WeightForm>();
		Long totalCount = 0L;
		try{
			List<Weight> records = weightDao.getWeightRecords( p_model, p_sortPagination );
			if(CollectionUtils.isNotEmpty(records)){
				Weight oldRecord = null;
				List<Weight> allRecords = weightDao.getEnabledEntitiesByModel( p_model, Weight.class );
				totalCount = weightDao.getEnabledEntitiesCountByModel( Weight.class, p_model );
				for(Weight record : records){
					Long date = record.getDate();
					WeightForm form = createFormFromModel( record );
					
					//calculate day difference
					oldRecord = null;
					oldRecord =  (Weight) CollectionUtils.find( allRecords, new WeightPredicate( applicationUtils.getDayBefore( date ) ) );
					form.setDayDifference(oldRecord != null ? record.getWeight() - oldRecord.getWeight() : 0d ); 
					
					//calculate week difference
					oldRecord = null;
					oldRecord =  (Weight) CollectionUtils.find( allRecords, new WeightPredicate( applicationUtils.getWeekBefore( date ) ) );
					form.setWeekDifference(oldRecord != null ? record.getWeight() - oldRecord.getWeight() : 0d ); 
					
					//calculate month difference
					oldRecord = null;
					oldRecord =  (Weight) CollectionUtils.find( allRecords, new WeightPredicate( applicationUtils.getMonthBefore( date ) ) );
					form.setMonthDifference(oldRecord != null ? record.getWeight() - oldRecord.getWeight() : 0d ); 
					
					formList.add(form);
				}		
			}
		}catch (Exception err) {
			log.error(messageSource.getMessage("log.error.load", new Object[]{err.getMessage(), LOG_ENTITY}, null));
			throw new AppException(messageSource.getMessage("message.error.common", null, null));
		}
		return new Object[]{formList, totalCount};
	}
	
	@Override
	@Transactional(readOnly=true)
	public WeightForm initWeightForm(Model p_model, Integer p_id) throws AppException {
		WeightForm form = null;
		if(p_id != null){
			Weight weight = (Weight) weightDao.getById( Weight.class, p_id );
			if(weight == null){
				throw new AppException( 
						messageSource.getMessage( "message.error.weight.not.found.by.id", new Object[]{p_id} , null ) );
			}else{
				form = createFormFromModel( weight );
			}
		}
		return form;
	}

	@Override
	@Transactional(readOnly=false)
	public WeightForm saveWeightForm(Model p_model, WeightForm p_weightForm) throws AppException {
		checkModel( p_model );
		validateWeightForm( p_weightForm );
		if(CollectionUtils.isNotEmpty( p_weightForm.getErrors() )){
			return p_weightForm;
		}
		Weight weight = weightDao.getEnabledWeightForModelById( p_model, p_weightForm.getId() );
		if(weight == null){
			throw new AppException( 
					messageSource.getMessage( "message.error.weight.not.found.by.id", new Object[]{p_weightForm.getId()} , null ) );
		}	
		weight.setWeight( p_weightForm.getWeight() );
		weight.setTrenning( p_weightForm.getTrenning() ? TRENNING : 0 );
		weightDao.saveOrUpdate( weight );
		return p_weightForm;
	}
	
	/**
	 * Creates form from model entity <br>
	 * @param p_record - model entity; {@link Weight} <br>
	 * @param p_eatingTypes - eating types list for setup fill flag; {@link List}<{@link EatingType}> <br>
	 * @return {@link WeightForm}
	 */
	private WeightForm createFormFromModel(Weight p_record){
		WeightForm form = new WeightForm();
		form.setId( p_record.getId() );
		form.setEnabled( p_record.getEnabled() );
		form.setModelId( p_record.getModel().getId() );
		form.setDate( applicationUtils.getDateDMYFromLong(p_record.getDate()) );
		form.setWeight( p_record.getWeight() );
		form.setTrenning( TRENNING.equals( p_record.getTrenning() ) ? true : false );
		if(CollectionUtils.isNotEmpty( p_record.getEatings() )){
			List<EatingForm> eatingForms = new ArrayList<EatingForm>();
			for(Eating eating : p_record.getEatings()){
				eatingForms.add( createEatingFormFromModel( eating ) );
			}
			form.setEatings( eatingForms );
			Collections.sort( form.getEatings() );
		}
		return form;
	}
	
	/**
	 * Creates eating form from model <br>
	 * @param p_record - model entity; {@link Eating} <br>
	 * @return {@link EatingForm}
	 */
	private EatingForm createEatingFormFromModel(Eating p_record){
		EatingForm form = new EatingForm();
		form.setId( p_record.getId() );
		form.setEnabled( p_record.getEnabled() );
		form.setTime( p_record.getTime());
		form.setFood( p_record.getFood() );
		form.setOverweight( OVERWEIGHT.equals( p_record.getOverweight() ) ? true :false );
		form.setEatingType( new EatingTypeForm(p_record.getEatingType()) );
		form.setEatingTypeId( form.getEatingType().getId());
		return form;
	}
	
	/**
	 * Validates weight form <br>
	 * @param p_weightForm - {@link WeightForm}
	 */
	private void validateWeightForm(WeightForm p_weightForm){
		p_weightForm.getErrors().clear();
		Double weight = p_weightForm.getWeight();
		if(weight == null){
			p_weightForm.getErrors().add(messageSource.getMessage( "message.error.weight.null", null, null ));
		}
		if(weight <= 0){
			p_weightForm.getErrors().add(messageSource.getMessage( "message.error.weight.min", null, null ));
		}
		if(weight >= AppConstants.MAX_WEIGHT){
			p_weightForm.getErrors().add(messageSource.getMessage( "message.error.weight.max", new Object[]{AppConstants.MAX_WEIGHT}, null ));
		}
	}
	
	/**
	 * Cretes weight record with defaul parameters<br>
	 * @param p_model - {@link Model}<br>
	 * @param p_date - {@link Long} <br>
	 * @param p_eatingTypes - {@link List}<{@link EatingType}>
	 * @return {@link Weight}
	 */
	private Weight createWeightForDate(Model p_model, Long p_date, List<EatingType> p_eatingTypes){
		Weight weight = new Weight();
		weight.setEnabled( AppConstants.ENTITY_STATE_ENABLED );
		weight.setModel( p_model );
		weight.setDate( p_date );
		weight.setWeight( 0d );
		weight.setTrenning((short) 0);
		if(CollectionUtils.isNotEmpty( p_eatingTypes )){
			Set<Eating> eatings = new TreeSet<Eating>();
			Eating eating = null;
			for(EatingType eatingType: p_eatingTypes){
				eating = new Eating();
				eating.setEatingType( eatingType );
				eating.setEnabled( AppConstants.ENTITY_STATE_ENABLED );
				eating.setFood( new String() );
				eating.setOverweight( (short) 0 );
				eating.setWeight( weight );
				eating.setTime( AppConstants.DEFAULT_EATING_TIME );
				eatings.add( eating );
			}
			weight.setEatings( eatings );
		}
		return weight;
	}
}
